﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace uTILLIty.Outlook.FlexFileAddIn
{
	[Serializable]
	public class SynchronizedBindingList<T> : BindingList<T>, ITypedList
	{
		[NonSerialized] private readonly PropertyDescriptorCollection _properties;

		private bool _notificationDelayed;

		public SynchronizedBindingList() : this(SynchronizationContext.Current)
		{
		}

		public SynchronizedBindingList(SynchronizationContext ctx)
		{
			SynchronizationContext = ctx;

			// Get the 'shape' of the list. 
			// Only get the public properties marked with Browsable = true.
			var pdc = TypeDescriptor.GetProperties(
				typeof(T),
				new Attribute[] {new BrowsableAttribute(true)});

			// Sort the properties.
			_properties = pdc.Sort();
		}

		public SynchronizationContext SynchronizationContext { get; set; }

		public new bool RaiseListChangedEvents
		{
			get { return base.RaiseListChangedEvents; }
			set
			{
				var doRaise = base.RaiseListChangedEvents && _notificationDelayed;
				base.RaiseListChangedEvents = value;
				if (doRaise)
					OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
			}
		}

		public void AddRange(IEnumerable<T> itemsToAdd)
		{
			foreach (var item in itemsToAdd)
			{
				Add(item);
			}
		}

		protected override void OnListChanged(ListChangedEventArgs e)
		{
			if (RaiseListChangedEvents)
			{
				_notificationDelayed = true;
				return;
			}
			TryApplySort();
			_notificationDelayed = false;
			if (SynchronizationContext == null)
			{
				base.OnListChanged(e);
			}
			else
			{
				SynchronizationContext.Send(delegate { base.OnListChanged(e); }, null);
			}
		}

		#region ITypedList Implementation

		public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
		{
			PropertyDescriptorCollection pdc;

			if (null == listAccessors)
			{
				// Return properties in sort order.
				pdc = _properties;
			}
			else
			{
				// Return child list shape.
				pdc = ListBindingHelper.GetListItemProperties(listAccessors[0].PropertyType);
			}

			return pdc;
		}

		// This method is only used in the design-time framework 
		// and by the obsolete DataGrid control.
		public string GetListName(PropertyDescriptor[] listAccessors)
		{
			return typeof(T).Name;
		}

		protected override bool SupportsSortingCore
		{
			get { return true; }
		}

		protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
		{
			_currentProperty = prop;
			_currentDirection = direction;
			TryApplySort();
		}

		protected virtual void TryApplySort()
		{
			if (_currentProperty == null)
				return;

			var list = (List<T>) Items;
			list.Sort(Compare);
		}

		[NonSerialized] private PropertyDescriptor _currentProperty;

		[NonSerialized] private ListSortDirection _currentDirection;

		private int Compare(T a, T b)
		{
			var valA = _currentProperty.GetValue(a);
			var valB = _currentProperty.GetValue(b);

			var icA = valA as IComparable;
			var icB = valB as IComparable;

			if (icA == null && icB == null)
			{
				return 0;
			}

			if (icA == null)
			{
				return _currentDirection == ListSortDirection.Ascending ? -1 : 1;
			}

			if (icB == null)
			{
				return _currentDirection == ListSortDirection.Ascending ? 1 : -1;
			}


			if (_currentDirection == ListSortDirection.Ascending)
			{
				return icA.CompareTo(icB);
			}
			return -icA.CompareTo(icB);
		}

		protected override void RemoveSortCore()
		{
		}

		#endregion
	}
}